/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.deltaspike.core.impl.exclude.extension; import org.apache.deltaspike.core.api.config.ConfigResolver; import org.apache.deltaspike.core.api.config.base.CoreBaseConfig; import org.apache.deltaspike.core.api.exclude.Exclude; import org.apache.deltaspike.core.impl.util.AnnotationInstanceUtils; import org.apache.deltaspike.core.util.metadata.builder.AnnotatedTypeBuilder; import org.apache.deltaspike.core.impl.exclude.CustomProjectStageBeanFilter; import org.apache.deltaspike.core.impl.exclude.GlobalAlternative; import org.apache.deltaspike.core.spi.activation.Deactivatable; import org.apache.deltaspike.core.api.interpreter.ExpressionInterpreter; import org.apache.deltaspike.core.api.projectstage.ProjectStage; import org.apache.deltaspike.core.impl.interpreter.PropertyExpressionInterpreter; import org.apache.deltaspike.core.util.ClassDeactivationUtils; import org.apache.deltaspike.core.util.ClassUtils; import org.apache.deltaspike.core.util.ProjectStageProducer; import javax.enterprise.event.Observes; import javax.enterprise.inject.Alternative; import javax.enterprise.inject.spi.AfterDeploymentValidation; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.BeforeBeanDiscovery; import javax.enterprise.inject.spi.Extension; import javax.enterprise.inject.spi.ProcessAnnotatedType; import javax.enterprise.util.Nonbinding; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.logging.Level; import java.util.logging.Logger; /** * <p>This class implements the logic for handling * {@link org.apache.deltaspike.core.api.exclude.Exclude} annotations.</p> * <p/> * <p>Further details see {@link org.apache.deltaspike.core.api.exclude.Exclude}</p> */ public class ExcludeExtension implements Extension, Deactivatable { private static final String GLOBAL_ALTERNATIVES = "globalAlternatives."; private static final Logger LOG = Logger.getLogger(ExcludeExtension.class.getName()); private boolean isActivated = true; private boolean isGlobalAlternativeActivated = true; private boolean isCustomProjectStageBeanFilterActivated = true; /** * Contains the globalAlternatives which should get used * KEY=Interface class name * VALUE=Implementation class name */ private Map<String, String> globalAlternatives = new HashMap<String, String>(); private Annotation priorityAnnotationInstance; @SuppressWarnings("UnusedDeclaration") protected void init(@Observes BeforeBeanDiscovery beforeBeanDiscovery, BeanManager beanManager) { isActivated = ClassDeactivationUtils.isActivated(getClass()); isCustomProjectStageBeanFilterActivated = ClassDeactivationUtils.isActivated(CustomProjectStageBeanFilter.class); isGlobalAlternativeActivated = ClassDeactivationUtils.isActivated(GlobalAlternative.class); if (isGlobalAlternativeActivated) { Map<String, String> allProperties = ConfigResolver.getAllProperties(); for (Map.Entry<String, String> property : allProperties.entrySet()) { if (property.getKey().startsWith(GLOBAL_ALTERNATIVES)) { String interfaceName = property.getKey().substring(GLOBAL_ALTERNATIVES.length()); String implementation = property.getValue(); if (LOG.isLoggable(Level.FINE)) { LOG.fine("Enabling global alternative for interface " + interfaceName + ": " + implementation); } globalAlternatives.put(interfaceName, implementation); } } if (globalAlternatives.isEmpty()) { isGlobalAlternativeActivated = false; } if (isGlobalAlternativeActivated) { int priorityValue = CoreBaseConfig.InterceptorCustomization.PRIORITY; priorityAnnotationInstance = AnnotationInstanceUtils.getPriorityAnnotationInstance(priorityValue); } } } /** * triggers initialization in any case * @param afterDeploymentValidation observed event */ @SuppressWarnings("UnusedDeclaration") protected void initProjectStage(@Observes AfterDeploymentValidation afterDeploymentValidation) { ProjectStageProducer.getInstance(); } /** * Observer which is vetoing beans based on {@link Exclude} * @param processAnnotatedType observed event */ @SuppressWarnings("UnusedDeclaration") protected void vetoBeans(@Observes ProcessAnnotatedType processAnnotatedType, BeanManager beanManager) { //we need to do it before the exclude logic to keep the @Exclude support for global alternatives if (isGlobalAlternativeActivated) { activateGlobalAlternatives(processAnnotatedType, beanManager); } if (isCustomProjectStageBeanFilterActivated) { vetoCustomProjectStageBeans(processAnnotatedType); } if (!isActivated) { return; } //TODO needs further discussions for a different feature CodiStartupBroadcaster.broadcastStartup(); //also forces deterministic project-stage initialization ProjectStage projectStage = ProjectStageProducer.getInstance().getProjectStage(); Exclude exclude = extractExcludeAnnotation(processAnnotatedType.getAnnotatedType().getJavaClass()); if (exclude == null) { return; } if (!evalExcludeWithoutCondition(processAnnotatedType, exclude)) { return; //veto called already } if (!evalExcludeInProjectStage(processAnnotatedType, exclude, projectStage)) { return; //veto called already } if (!evalExcludeNotInProjectStage(processAnnotatedType, exclude, projectStage)) { return; //veto called already } evalExcludeWithExpression(processAnnotatedType, exclude); } //only support the physical usage and inheritance if @Exclude comes from an abstract class //TODO re-visit the impact of java.lang.annotation.Inherited (for @Exclude) for the available use-cases protected Exclude extractExcludeAnnotation(Class<?> currentClass) { Exclude result = currentClass.getAnnotation(Exclude.class); if (result != null) { return result; } currentClass = currentClass.getSuperclass(); while (!Object.class.equals(currentClass) && currentClass != null) { if (Modifier.isAbstract(currentClass.getModifiers())) { result = currentClass.getAnnotation(Exclude.class); } if (result != null) { return result; } currentClass = currentClass.getSuperclass(); } return null; } protected void vetoCustomProjectStageBeans(ProcessAnnotatedType processAnnotatedType) { //currently there is a veto for all project-stage implementations, //but we still need @Typed() for the provided implementations in case of the deactivation of this behaviour if (ProjectStage.class.isAssignableFrom(processAnnotatedType.getAnnotatedType().getJavaClass())) { processAnnotatedType.veto(); } } private void activateGlobalAlternatives(ProcessAnnotatedType processAnnotatedType, BeanManager beanManager) { Class<Object> currentBean = processAnnotatedType.getAnnotatedType().getJavaClass(); if (currentBean.isInterface()) { return; } Set<Class> beanBaseTypes = resolveBeanTypes(currentBean); boolean isAlternativeBeanImplementation = currentBean.isAnnotationPresent(Alternative.class); List<Annotation> qualifiersOfCurrentBean = resolveQualifiers(processAnnotatedType.getAnnotatedType().getAnnotations(), beanManager); String configuredBeanName; List<Annotation> qualifiersOfConfiguredBean; Class<Object> alternativeBeanClass; Set<Annotation> alternativeBeanAnnotations; for (Class currentType : beanBaseTypes) { alternativeBeanAnnotations = new HashSet<Annotation>(); configuredBeanName = globalAlternatives.get(currentType.getName()); if (configuredBeanName != null && configuredBeanName.length() > 0) { alternativeBeanClass = ClassUtils.tryToLoadClassForName(configuredBeanName); if (alternativeBeanClass == null) { throw new IllegalStateException("Can't find class " + configuredBeanName + " which is configured" + " for " + currentType.getName()); } //check that the configured class is an alternative if (!alternativeBeanClass.isAnnotationPresent(Alternative.class)) { //we have to continue because other classes can be configured as well continue; } alternativeBeanAnnotations.addAll(Arrays.asList(alternativeBeanClass.getAnnotations())); qualifiersOfConfiguredBean = resolveQualifiers(alternativeBeanAnnotations, beanManager); } else { continue; } if (!doQualifiersMatch(qualifiersOfCurrentBean, qualifiersOfConfiguredBean)) { continue; } //current bean is annotated with @Alternative and of the same type as the configured bean if (isAlternativeBeanImplementation && alternativeBeanClass.equals(currentBean)) { LOG.info(processAnnotatedType.getAnnotatedType().getJavaClass().getName() + " is configured as global-alternative"); //cdi 1.0 if (priorityAnnotationInstance == null) { AnnotatedTypeBuilder<Object> annotatedTypeBuilder = new AnnotatedTypeBuilder<Object>().readFromType(processAnnotatedType.getAnnotatedType()); annotatedTypeBuilder.removeFromClass(Alternative.class); processAnnotatedType.setAnnotatedType(annotatedTypeBuilder.create()); return; } //cdi 1.1+ else { AnnotatedTypeBuilder<Object> annotatedTypeBuilder = new AnnotatedTypeBuilder<Object>().readFromType(processAnnotatedType.getAnnotatedType()); annotatedTypeBuilder.addToClass(priorityAnnotationInstance); processAnnotatedType.setAnnotatedType(annotatedTypeBuilder.create()); return; } } else //current bean is the original implementation { //cdi 1.0 (no change needed with cdi 1.1+) if (priorityAnnotationInstance == null) { //veto this original implementation because the alternative will be added processAnnotatedType.veto(); return; } } } } private boolean doQualifiersMatch(List<Annotation> qualifiersOfCurrentBean, List<Annotation> qualifiersOfConfiguredBean) { if (qualifiersOfCurrentBean.size() != qualifiersOfConfiguredBean.size()) { return false; } int matchingQualifiers = 0; for (Annotation currentQualifier : qualifiersOfCurrentBean) { for (Annotation qualifierConfiguredBean : qualifiersOfConfiguredBean) { if (doesQualifierMatch(currentQualifier, qualifierConfiguredBean)) { matchingQualifiers++; break; } } } return qualifiersOfConfiguredBean.size() == matchingQualifiers; } private boolean doesQualifierMatch(Annotation currentQualifier, Annotation qualifierConfiguredBean) { if (!currentQualifier.annotationType().equals(qualifierConfiguredBean.annotationType())) { return false; } Object currentValue; Object valueOfQualifierConfiguredBean; for (Method currentMethod : currentQualifier.annotationType().getDeclaredMethods()) { if (currentMethod.isAnnotationPresent(Nonbinding.class)) { continue; } try { currentMethod.setAccessible(true); currentValue = currentMethod.invoke(currentQualifier); valueOfQualifierConfiguredBean = currentMethod.invoke(qualifierConfiguredBean); if (!currentValue.equals(valueOfQualifierConfiguredBean)) { return false; } } catch (Exception e) { throw new IllegalStateException("Can't compare " + currentQualifier.annotationType().getName() + " with " + qualifierConfiguredBean.annotationType().getName(), e); } } return true; } private List<Annotation> resolveQualifiers(Set<Annotation> annotations, BeanManager beanManager) { List<Annotation> result = new ArrayList<Annotation>(); for (Annotation annotation : annotations) { if (beanManager.isQualifier(annotation.annotationType())) { result.add(annotation); } } return result; } private Set<Class> resolveBeanTypes(Class beanClass) { Set<Class> result = new HashSet<Class>(); Class<?> currentClass = beanClass; while (currentClass != null && !Object.class.getName().equals(currentClass.getName())) { result.add(currentClass); for (Class interfaceClass : currentClass.getInterfaces()) { if (interfaceClass.getName().startsWith("java.") || interfaceClass.getName().startsWith("javax.")) { continue; } result.addAll(resolveBeanTypes(interfaceClass)); } currentClass = currentClass.getSuperclass(); } return result; } private boolean evalExcludeWithoutCondition(ProcessAnnotatedType processAnnotatedType, Exclude exclude) { if (exclude.ifProjectStage().length == 0 && exclude.exceptIfProjectStage().length == 0 && "".equals(exclude.onExpression())) { veto(processAnnotatedType, "Stateless"); return false; } return true; } private boolean evalExcludeInProjectStage(ProcessAnnotatedType processAnnotatedType, Exclude exclude, ProjectStage currentlyConfiguredProjectStage) { Class<? extends ProjectStage>[] activatedIn = exclude.ifProjectStage(); if (activatedIn.length == 0) { return true; } if (isInProjectStage(activatedIn, currentlyConfiguredProjectStage)) { veto(processAnnotatedType, "IfProjectState"); return false; } return true; } private boolean evalExcludeNotInProjectStage(ProcessAnnotatedType processAnnotatedType, Exclude exclude, ProjectStage currentlyConfiguredProjectStage) { Class<? extends ProjectStage>[] notIn = exclude.exceptIfProjectStage(); if (notIn.length == 0) { return true; } if (!isInProjectStage(notIn, currentlyConfiguredProjectStage)) { veto(processAnnotatedType, "ExceptIfProjectState"); return false; } return true; } private void evalExcludeWithExpression(ProcessAnnotatedType processAnnotatedType, Exclude exclude) { if ("".equals(exclude.onExpression())) { return; } if (isDeactivated(exclude, PropertyExpressionInterpreter.class)) { veto(processAnnotatedType, "Expression"); } } private boolean isInProjectStage(Class<? extends ProjectStage>[] activatedIn, ProjectStage currentlyConfiguredProjectStage) { if (activatedIn != null && activatedIn.length > 0) { for (Class<? extends ProjectStage> activated : activatedIn) { if (currentlyConfiguredProjectStage.getClass().equals(activated)) { return true; } } } return false; } private boolean isDeactivated(Exclude exclude, Class defaultExpressionInterpreterClass) { String expressions = exclude.onExpression(); Class<? extends ExpressionInterpreter> interpreterClass = exclude.interpretedBy(); if (interpreterClass.equals(ExpressionInterpreter.class)) { interpreterClass = defaultExpressionInterpreterClass; } ExpressionInterpreter<String, Boolean> expressionInterpreter = ClassUtils.tryToInstantiateClass(interpreterClass); if (expressionInterpreter == null) { if (LOG.isLoggable(Level.WARNING)) { LOG.warning("can't instantiate " + interpreterClass.getClass().getName()); } return true; } return expressionInterpreter.evaluate(expressions); } private void veto(ProcessAnnotatedType processAnnotatedType, String vetoType) { processAnnotatedType.veto(); LOG.finer(vetoType + " based veto for bean with type: " + processAnnotatedType.getAnnotatedType().getJavaClass()); } private static String getJarVersion(Class targetClass) { String manifestFileLocation = getManifestFileLocationOfClass(targetClass); try { return new Manifest(new URL(manifestFileLocation).openStream()) //weld doesn't use IMPLEMENTATION_VERSION .getMainAttributes().getValue(Attributes.Name.SPECIFICATION_VERSION); } catch (Exception e) { return null; } } private static String getManifestFileLocationOfClass(Class targetClass) { String manifestFileLocation; try { manifestFileLocation = getManifestLocation(targetClass); } catch (Exception e) { //in this case we have a proxy manifestFileLocation = getManifestLocation(targetClass.getSuperclass()); } return manifestFileLocation; } private static String getManifestLocation(Class targetClass) { String classFilePath = targetClass.getCanonicalName().replace('.', '/') + ".class"; String manifestFilePath = "/META-INF/MANIFEST.MF"; String classLocation = targetClass.getResource(targetClass.getSimpleName() + ".class").toString(); return classLocation.substring(0, classLocation.indexOf(classFilePath) - 1) + manifestFilePath; } }